36. SD卡

您所在的位置:网站首页 闪迪sd卡32 g300mb 36. SD卡

36. SD卡

2024-07-04 18:13| 来源: 网络整理| 查看: 265

36.5. SD卡读写测试实验¶

SD卡广泛用于便携式设备上,比如数码相机、手机、多媒体播放器等。对于嵌入式设备来说是一种重要的存储数据部件。 类似于SPI Flash芯片数据操作,可以直接进行读写,也可以写入文件系统,然后使用文件系统读写函数,使用文件系统操作。 本实验是进行SD卡最底层的数据读写操作,直接使用SPI接口对SD卡进行读写,会损坏SD卡原本内容,导致数据丢失, 实验前请注意备份SD卡的原内容。由于SD卡容量很大,我们平时使用的SD卡都是已经包含有文件系统的,一般不会使用本章的操作方式编写SD卡的应用, 但它是SD卡操作的基础,对于原理学习是非常有必要的,在它的基础上移植文件系统到SD卡的应用将在下一章讲解。

36.5.1. 硬件设计¶

本开发板采用STM32的SPI外设接口驱动SD卡,与SPI驱动外部FLASH类似,其片选信号线CS使用普通的GPIO,采用软件控制, STM32与SD卡卡槽的硬件连接见图 SD卡硬件设计。

对于STM32的SPI外设控制原理,本章不再详细分析,请参考前面的SPI FLASH章节。

36.5.2. 软件设计¶

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等没有全部罗列出来,完整的代码请参考本章配套的工程。 有了相关SD卡驱动相关的知识基础,我们就可以着手开始编写SD卡驱动程序了,根据之前内容,可了解操作的大概流程:

初始化相关GPIO及SPI外设;

配置SD卡进入SPI模式,通过几个命令获取卡的空间大小等信息;

对SD卡进行进行读写的操作。

虽然看起来只有三步,但它们有非常多的细节需要处理。实际上,SD卡是非常常用外设部件,ST公司在其测试板上也有板子SD卡卡槽, 并提供了完整的驱动程序,我们直接参考移植使用即可。类似SDIO、USB这些复杂的外设,它们的通信协议相当庞大,要自行编写完整、 严谨的驱动不是一件轻松的事情,这时我们就可以利用ST官方例程的驱动文件,根据自己硬件移植到自己开发平台即可。

在“初识STM32标准库”章节我们重点讲解了标准库的源代码及启动文件和库使用帮助文档这两部分内容,实际上“Utilities”文件夹内容是非常有参考价值的, 该文件夹包含了基于ST官方实验板的驱动文件,比如LCD、SRAM、SD卡、音频解码IC等等底层驱动程序,另外还有第三方软件库, 如emWin图像软件库和FatFs文件系统。虽然,我们的开发平台跟ST官方实验平台硬件设计略有差别,但移植程序方法是完全可行的。 学会移植程序可以减少很多工作量,加快项目进程,更何况ST官方的驱动代码是经过严格验证的。

在ST固件库“STM32F10x_StdPeriph_Lib_V3.5.0\Utilities\STM32_EVAL\Common”文件夹下可以找到SD卡驱动文件, 见图 ST官方实验板SD卡驱动文件。我们需要stm32_eval_spi_sd.c和stm32_eval_spi_sd.h两个文件的完整内容。 另外还可以参考目录“STM32F10x_StdPeriph_Lib_V3.5.0\UtilitiesSTM32_EVAL\STM3210C_EVAL”下的stm3210c_eval.c文件底层GPIO初始化示例代码, 如SD_LowLevel_Init初始化SPI的GPIO及模式的函数。

为简化工程,本章的配置工程把上述文件中与SD卡配置相关的代码都整合到bsp_sdio_sdcard.c和bsp_sdio_sdcard.h文件中, 见图 SD卡驱动工程文件结构。另外,还自行编写了sdio_test.c和sdio_test.h文件,这两个文件包含了对SD卡进行读写测试的代码。

本实验中讲解的代码,大部分是从ST提供的这个SD卡驱动示例整理而来,不过,官方提供的这个驱动仅支持SDSC卡,不支持SDHC卡, 也就是说超出2G的卡使用有问题,但经过我们的修改,本实验呈现出的SD驱动文件适用于SDSC及SDHC,也就是说32G以下的SD卡都能正常驱动。

36.5.2.1. GPIO初始化和SPI配置¶

本实验使用STM32的SPI外设驱动SD卡,其控制原理与SPI驱动外部FLASH的一样。

SPI引脚相关的宏定义

类似地,工程中首先把引脚号、时钟等硬件相关的配置写入到驱动的头文件,见 代码清单:SD-1。

代码清单:SD-1 SPI引脚相关的宏定义(bsp_spi_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23#define SD_SPI SPI1 #define SD_SPI_CLK RCC_APB2Periph_SPI1 #define SD_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd #define SD_SPI_SCK_PIN GPIO_Pin_5 #define SD_SPI_SCK_GPIO_PORT GPIOA #define SD_SPI_SCK_GPIO_CLK RCC_APB2Periph_GPIOA #define SD_SPI_MISO_PIN GPIO_Pin_6 #define SD_SPI_MISO_GPIO_PORT GPIOA #define SD_SPI_MISO_GPIO_CLK RCC_APB2Periph_GPIOA #define SD_SPI_MOSI_PIN GPIO_Pin_7 #define SD_SPI_MOSI_GPIO_PORT GPIOA #define SD_SPI_MOSI_GPIO_CLK RCC_APB2Periph_GPIOA #define SD_CS_PIN GPIO_Pin_8 #define SD_CS_GPIO_PORT GPIOA #define SD_CS_GPIO_CLK RCC_APB2Periph_GPIOA //#define SD_DETECT_PIN GPIO_Pin_0 //#define SD_DETECT_GPIO_PORT GPIOE //#define SD_DETECT_GPIO_CLK RCC_APB2Periph_GPIOE

上述代码定义了使用的硬件SPI号,CLK、MOSI、MISO及CS引脚,其中CS引脚采用软件控制,所以硬件设计时也只是选择了一个普通的GPIO。 代码最后注释掉的SD_DETECT_PIN是用于检测SD卡是否接入到卡槽的,它通过检测该引脚电平的高低来实现判断,不过本开发板硬件设计上并没有连接卡槽的检测引脚, 所以检测引脚相关的代码被注释掉了。实际上不使用该引脚也可以检测SD卡是否接入,例如通过向SD卡发送命令,然后通过检测是否有正确的响应来判断SD卡是否接入正常。

GPIO初始化及SPI模式配置

代码清单:SD-2 GPIO初始化(bsp_spi_sdcard.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47static void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; /*!< 使能引脚相关的时钟 */ RCC_APB2PeriphClockCmd(SD_CS_GPIO_CLK | SD_SPI_MOSI_GPIO_CLK | SD_SPI_MISO_GPIO_CLK | SD_SPI_SCK_GPIO_CLK , ENABLE); /*!< 使能SPI时钟 */ SD_SPI_APBxClock_FUN(SD_SPI_CLK, ENABLE); /*!< 配置 SD_SPI 引脚: SCK */ GPIO_InitStructure.GPIO_Pin = SD_SPI_SCK_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(SD_SPI_SCK_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SD_SPI 引脚: MOSI */ GPIO_InitStructure.GPIO_Pin = SD_SPI_MOSI_PIN; GPIO_Init(SD_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SD_SPI 引脚: MISO */ GPIO_InitStructure.GPIO_Pin = SD_SPI_MISO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(SD_SPI_MISO_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SD_SPI_CS_PIN 引脚: SD Card CS pin */ GPIO_InitStructure.GPIO_Pin = SD_CS_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(SD_CS_GPIO_PORT, &GPIO_InitStructure); /*!< SD_SPI 配置 SPI模式3 */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SD_SPI, &SPI_InitStructure); SPI_Cmd(SD_SPI, ENABLE); /*!< SD_SPI 使能 */ }

类似地,本函数按照SPI外设的要求,把CLK、MOSI引脚配置成复用推挽输出,MISO配置成浮空输入, 而软件控制的CS引脚配置为普通推挽输出,以便直接控制其输出高低电平。

在SPI外设模式的配置方面,最重要的是必须按照SD卡协议的要求使用SPI的模式3,即前面 SD卡寄存器 说明中的空闲时SCK时钟为高电平、 采样时刻为偶数边沿的配置,该配置反映到以上代码就是SPI初始化结构体成员CPOL和SPI_CPHA分别被配置为宏SPI_CPOL_High及SPI_CPHA_2Edge。 其余的SPI模式配置很好理解,跟SPI驱动FLASH是类似的,其中用于配置CRC校验的SPI_CRCPolynomial配置实际上并没有用到,所以把它配置成任意值都是可以的。

收发一个字节数据

代码清单:SD-3 收发一个字节数据(bsp_spi_sdcard.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51#define SD_DUMMY_BYTE 0xFF /** * @brief 向SD卡发送一个字节. * @param Data: 要发送的数据. * @retval 接收到的数据 */ uint8_t SD_WriteByte(uint8_t Data) { /*!< 等待至发送缓冲区为空*/ while (SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_TXE) == RESET) { } /*!< 发送一个字节 */ SPI_I2S_SendData(SD_SPI, Data); /*!< SPI是全双工协议,发送的时候同时会接收到数据*/ /*等待接收完成*/ while (SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_RXNE) == RESET) { } /*!< 返回接收到的数据 */ return SPI_I2S_ReceiveData(SD_SPI); } /** * @brief 从SD卡读取一个字节 * @param None * @retval 接收到的数据 */ uint8_t SD_ReadByte(void) { uint8_t Data = 0; /*!< 等待至发送缓冲区为空*/ while (SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_TXE) == RESET) { } /*!< 发送一个 空 字节 */ SPI_I2S_SendData(SD_SPI, SD_DUMMY_BYTE); /*!< SPI是全双工协议,发送的时候同时会接收到数据*/ /*等待接收完成*/ while (SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_RXNE) == RESET) { } /*!< 读取接收到的数据 */ Data = SPI_I2S_ReceiveData(SD_SPI); /*!< 返回接收到的数据 */ return Data; }

与SPI读写FLASH章节的控制原理类似,使用SPI收发一个字节时,利用库函数SPI_I2S_GetFlagStatus检查收发缓冲区, 当发送缓冲区为空的时候调用SPI_I2S_SendData发送一个字节,由于SPI协议是全双工的,即发送一个字节的时候会同时接收到从机返回的一个字节内容, 这时再调用SPI_I2S_GetFlagStatus检查接收缓冲区,当数据存在的时候,调用SPI_I2S_ReceiveData函数读取并返回。

SD_WriteByte和SD_ReadByte这两个函数并没有包含对片选信号线CS的控制,这是为了方便编写多字节通讯过程的控制函数, 在这些通讯过程中的CS片选信号高低电平切换期间可能调用了多次这两个读写函数,因而把CS信号线的控制放在函数外。

SD_WriteByte和SD_ReadByte这两个函数的主体区别不大,只是在发送数据时SD_WriteByte发送的是输入参数, 而SD_ReadByte发送的是无意义的的SD_DUMMY_BYTE(0xFF)。对于这个读取数据过程也要调用库函数SPI_I2S_SendData发送一个无意义字节的原因是: SPI通讯协议的时钟由主机产生(即STM32),而STM32通过调用SPI_I2S_SendData发送一个字节即可产生一个字节的时钟, SPI_I2S_SendData函数通过MOSI信号线发送的数据会被SD卡忽略,而发送数据的同时从设备(即SD卡)在该时钟的驱动下通过MISO信号线返回数据, 所以此处的SD_DUMMY_BYTE是无意义的,可以替换成其它数据。不过要注意的是SD_DUMMY_BYTE宏在本工程的初始化流程中也有使用, 该流程中SD_DUMMY_BYTE的宏值必须为0xFF,所以可以尝试直接把本代码SD_ReadByte函数里发送的SD_DUMMY_BYTE直接改成其它数据进行实验测试, 如把“SPI_I2S_SendData(SD_SPI, SD_DUMMY_BYTE) ”改成“SPI_I2S_SendData(SD_SPI, 0x00) ”, 这样修改程序是能正常运行的,但如果直接修改SD_DUMMY_BYTE宏的数值,会因初始化过程而失败。

36.5.2.2. 相关类型定义¶

打开bsp_spi_sdcard.h文件可以发现有非常多的枚举类型定义、结构体类型定义以及宏定义,在源文件里将会用到,此处先提前介绍一下。

R1响应及数据响应Token

代码清单:SD-4 响应枚举类型(bsp_spi_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22typedef enum { /** * @brief SD 响应及错误标志 */ SD_RESPONSE_NO_ERROR = (0x00), SD_IN_IDLE_STATE = (0x01), SD_ERASE_RESET = (0x02), SD_ILLEGAL_COMMAND = (0x04), SD_COM_CRC_ERROR = (0x08), SD_ERASE_SEQUENCE_ERROR = (0x10), SD_ADDRESS_ERROR = (0x20), SD_PARAMETER_ERROR = (0x40), SD_RESPONSE_FAILURE = (0xFF), /** * @brief 数据响应类型 */ SD_DATA_OK = (0x05), SD_DATA_CRC_ERROR = (0x0B), SD_DATA_WRITE_ERROR = (0x0D), SD_DATA_OTHER_ERROR = (0xFF) } SD_Error;

这个SD_Error枚举类型定义的分成了两部分,第一部分是R1响应类型,见前面图 R1响应类型说明 及其说明, 这些枚举定义正是遵照R1响应的各个位表示的状态,当STM32接收到R1响应时,可以利用这些枚举定义进行比较判断;第二部分是数据响应Token, 见前面图 数据响应Token的格式 及其说明,把数据响应Token的最高3个无关的位设置成0, 然后组合起Status及其余两位的值得到一个字节数字,这正是代码中对应状态枚举定义的数值。

数据块开始和停止的Token

代码清单:SD-5 数据块开始和停止的Token(bsp_spi_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19/** * @brief Start Data tokens: * Tokens (necessary because at nop/idle (and CS active) only 0xff is * on the data/command line) */ /*!< Data token start byte, 单块读起始Token */ #define SD_START_DATA_SINGLE_BLOCK_READ 0xFE /*!< Data token start byte, 多块读起始Token */ #define SD_START_DATA_MULTIPLE_BLOCK_READ 0xFE /*!< Data token start byte, 单块写起始Token */ #define SD_START_DATA_SINGLE_BLOCK_WRITE 0xFE /*!< Data token start byte, 多块写起始Token */ #define SD_START_DATA_MULTIPLE_BLOCK_WRITE 0xFC /*!< Data toke stop byte, 多块写停止Token */ #define SD_STOP_DATA_MULTIPLE_BLOCK_WRITE 0xFD

这部分代码定义的是前面介绍的数据块开始和停止的Token,对于单块读写及多块的读数据Token均为“0xFE”, 而多块写入的开始Token值为“0xFC”,结束Token值为“0xFD”。

SD控制命令

代码清单:SD-6 SD控制命令(bsp_spi_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29/** * @brief Commands: CMDxx = CMD-number | 0x40 */ #define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */ #define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */ #define SD_CMD_SEND_CSD 9 /*!< CMD9 = 0x49 */ #define SD_CMD_SEND_CID 10 /*!< CMD10 = 0x4A */ #define SD_CMD_STOP_TRANSMISSION 12 /*!< CMD12 = 0x4C */ #define SD_CMD_SEND_STATUS 13 /*!< CMD13 = 0x4D */ #define SD_CMD_SET_BLOCKLEN 16 /*!< CMD16 = 0x50 */ #define SD_CMD_READ_SINGLE_BLOCK 17 /*!< CMD17 = 0x51 */ #define SD_CMD_READ_MULT_BLOCK 18 /*!< CMD18 = 0x52 */ #define SD_CMD_SET_BLOCK_COUNT 23 /*!< CMD23 = 0x57 */ #define SD_CMD_WRITE_SINGLE_BLOCK 24 /*!< CMD24 = 0x58 */ #define SD_CMD_WRITE_MULT_BLOCK 25 /*!< CMD25 = 0x59 */ #define SD_CMD_PROG_CSD 27 /*!< CMD27 = 0x5B */ #define SD_CMD_SET_WRITE_PROT 28 /*!< CMD28 = 0x5C */ #define SD_CMD_CLR_WRITE_PROT 29 /*!< CMD29 = 0x5D */ #define SD_CMD_SEND_WRITE_PROT 30 /*!< CMD30 = 0x5E */ #define SD_CMD_SD_ERASE_GRP_START 32 /*!< CMD32 = 0x60 */ #define SD_CMD_SD_ERASE_GRP_END 33 /*!< CMD33 = 0x61 */ #define SD_CMD_UNTAG_SECTOR 34 /*!< CMD34 = 0x62 */ #define SD_CMD_ERASE_GRP_START 35 /*!< CMD35 = 0x63 */ #define SD_CMD_ERASE_GRP_END 36 /*!< CMD36 = 0x64 */ #define SD_CMD_UNTAG_ERASE_GROUP 37 /*!< CMD37 = 0x65 */ #define SD_CMD_ERASE 38 /*!< CMD38 = 0x66 */ #define SD_CMD_READ_OCR 58 /*!< CMD58 */ #define SD_CMD_APP_CMD 55 /*!< CMD55 返回0x01*/ #define SD_ACMD_SD_SEND_OP_COND 41 /*!< ACMD41 返回0x00*/

以上代码定义的是驱动代码中使用到的SD命令,SD命令本质上也是一个数值, 控制时由SPI主机通过MOSI信号线发送到SD卡,SD卡接收后解释成该值对应的命令然后执行。

以下请配合前面的说明图 SD命令格式 来理解,根据SD协议,命令号是一个6位的数字, 如CMD0的命令号为0,CMD1的命令号为1,此处宏定义的正是各个命令的命令号, 如宏SD_CMD_GO_IDLE_STATE(宏值为0)是CMD0,宏SD_CMD_SEND_OP_COND(宏值为1)是CMD1命令。

在上述每行宏定义代码注释中的数字比较特别,如“CMD0 = 0x40”,实际上该注释表示的都是命令号与数值0x40运算后的结果, 即作“CMDxx = 命令号 | 0x40”运算,那此处的0x40是什么,为什么注释要这样写呢?这是因为在SPI模式下,数据是一个字节一个字节地发送出去的, 一个完整的SD命令包含起始位、传输标志、命令号、参数、CRC7校验以及终止位,一共是48位即6个字节。而起始位和传输标志分别固定为0和1, 这两位与紧接着的6位命令号刚好组成1个字节,由于SPI是高位先行,所以前面的起始位和传输标志占高2位,即0x40,再跟后面的6位命令号作或运算, 组成1个字节,然后就可以直接通过SPI发送出去了。

CSD、CID及SD卡信息结构体

代码清单:SD-7 CSD、CID及SD卡信息结构体类型(bsp_spi_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39/** * @brief Card Specific Data: CSD 寄存器 */ typedef struct { __IO uint8_t CSDStruct; /*!< CSD structure */ __IO uint8_t SysSpecVersion; /*!< System specification version */ __IO uint8_t Reserved1; /*!< Reserved */ /*此处省略大量内容,具体请直接查看工程代码...*/ __IO uint8_t FileFormat; /*!< File Format */ __IO uint8_t ECC; /*!< ECC code */ __IO uint8_t CSD_CRC; /*!< CSD CRC */ __IO uint8_t Reserved4; /*!< always 1*/ } SD_CSD; /** * @brief Card Identification Data: CID 寄存器 */ typedef struct { __IO uint8_t ManufacturerID; /*!< ManufacturerID */ __IO uint16_t OEM_AppliID; /*!< OEM/Application ID */ __IO uint32_t ProdName1; /*!< Product Name part1 */ __IO uint8_t ProdName2; /*!< Product Name part2*/ __IO uint8_t ProdRev; /*!< Product Revision */ __IO uint32_t ProdSN; /*!< Product Serial Number */ __IO uint8_t Reserved1; /*!< Reserved1 */ __IO uint16_t ManufactDate; /*!< Manufacturing Date */ __IO uint8_t CID_CRC; /*!< CID CRC */ __IO uint8_t Reserved2; /*!< always 1 */ } SD_CID; /** * @brief SD Card information */ typedef struct { SD_CSD SD_csd; SD_CID SD_cid; uint32_t CardCapacity; /*!< Card Capacity */ uint32_t CardBlockSize; /*!< Card Block Size */ } SD_CardInfo;

以上代码定义了三个结构体,SD_CSD和SD_CID结构体用于存储从SD卡读取回来的CSD、CID寄存器的内容,根据这些寄存器的内容可以获知SD卡工作电压、 数据块的大小、卡的容量和序列号等信息,关于这两个寄存器的内容可查阅SD协议文档了解,与驱动最相关的信息是卡容量及数据块大小这两个信息, 而这些信息以及SD_CSD、SD_CID的内容又被封装进了SD_CardInfo结构体,驱动程序直接通过SD_CardInfo类型的结构体变量获取这些信息。

SD卡类型及全局变量

代码清单:SD-8 CSD、CID及SD卡信息结构体类型¶ 1 2 3 4 5 6 7 8 9 10/*C文件*/ uint8_t SD_Type = SD_TYPE_NOT_SD; //存储卡的类型 SD_CardInfo SDCardInfo; //用于存储卡的信息 /*H文件*/ //SD卡的类型 #define SD_TYPE_NOT_SD 0 //非SD卡 #define SD_TYPE_V1 1 //V1.0的卡 #define SD_TYPE_V2 2 //SDSC #define SD_TYPE_V2HC 4 //SDHC

这部分代码列出的是c文件里定义的全局SD_Type变量及SDCardInfo变量,在卡识别流程时将会向SD_Type变量赋值为宏定义中的非SD卡、 V1.0卡、V2的SDSC卡以及V2的SDHC卡,便于后续程序驱动时使用。SDCardInfo变量则是前面说明的卡信息结构体的实例变量, 驱动程序可通过该变量获取卡容量、块大小等信息。

36.5.2.3. 发送命令¶ 代码清单:SD-9 发送命令(bsp_spi_sdcard.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29/** * @brief 发送SD命令 * @param Cmd: 要发送的命令 * @param Arg: 命令参数 * @param Crc: CRC校验码. * @retval None */ void SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc) { uint32_t i = 0x00; uint8_t Frame[6]; Frame[0] = (Cmd | 0x40); /*!< Construct byte 1 */ Frame[1] = (uint8_t)(Arg >> 24); /*!< Construct byte 2 */ Frame[2] = (uint8_t)(Arg >> 16); /*!< Construct byte 3 */ Frame[3] = (uint8_t)(Arg >> 8); /*!< Construct byte 4 */ Frame[4] = (uint8_t)(Arg); /*!< Construct byte 5 */ Frame[5] = (Crc); /*!< Construct CRC: byte 6 */ for (i = 0; i > 2; SD_csd->Reserved1 = CSD_Tab[0] & 0x03; /*!< 字节 1 处理*/ SD_csd->TAAC = CSD_Tab[1]; /*!< 字节 2 处理*/ SD_csd->NSAC = CSD_Tab[2]; /*!< 以下省略3~15字节的处理......*/ /*!< 返回接收状态 */ return rvalue; }

这个函数SD_GetCSDRegister的主要流程如下:

(1) 拉低片选信号,调用SD_SendCmd向SD卡发送CMD9命令,SD卡接收到该命令后,将会返回一个字节的R1响应、 一个字节的单块数据读取Token、16个字节的CSD寄存器内容、2个字节的CRC校验码;

(2) 调用SD_GetResponse等待SD卡的R1响应;

(3) 接收到R1响应后,再调用SD_GetResponse检查SD卡是否返回单块数据读取的Token, 该宏SD_START_DATA_SINGLE_BLOCK_READ表示的Token值为0xFE;

(4) 接收到Token, 使用for循环调用SD_ReadByte函数接收长度为16个字节的CSD寄存器的值;

(5) 在16个字节的CSD寄存器内容之后,SD卡还返回2个字节的CRC校验码, 此处不进行校验,但还是要让SD卡完成发送流程,所以调用SD_WriteByte函数2次以产生时钟驱动SD卡输出校验码;

(6) 接收完寄存器的内容后,SD_GetCSDRegister函数根据CSD寄存器的定义, 把内容整理至SD_CSD结构体中,方便在其它程序直接通过该结构体访问特定的CSD信息。

在本工程中,还有一个SD_GetCIDRegister函数用于读取CID寄存器的值,该函数的处理过程与这个SD_GetCSDRegister函数类似, 区别仅为发送的命令不同(读取CID时发送CMD10)以及接收到的内容根据CID寄存器的定义整理到SD_CID结构体中,本教程不再详细讲解。

为了方便使用,程序里还把SD_GetCSDRegister、SD_GetCIDRegister函数封装到 SD_GetCardInfo函数中, 并根据读取得的CSD信息计算出卡的容量与数据块大小,见 代码清单:SD-16。

代码清单:SD-16 SD_GetCardInfo函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33/** * @brief 获取SD卡的信息. * @param SD_CardInfo 结构体指针 * @retval SD响应: * - SD_RESPONSE_FAILURE: 失败 * - SD_RESPONSE_NO_ERROR: 成功 */ SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) { SD_Error status = SD_RESPONSE_FAILURE; //读取CSD寄存器 status = SD_GetCSDRegister(&(cardinfo->SD_csd)); //读取CID寄存器 status = SD_GetCIDRegister(&(cardinfo->SD_cid)); if ((SD_Type == SD_TYPE_V1) || (SD_Type == SD_TYPE_V2)) { //块数目基数: CSize + 1 cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) ; //块数目 = 块数目基数*块数目乘数。块数目乘数: 2的 (C_SIZE_MULT + 2)次方 cardinfo->CardCapacity *= (1 CardBlockSize = 1 CardCapacity *= cardinfo->CardBlockSize; } else if (SD_Type == SD_TYPE_V2HC) { //SDHC卡 cardinfo->CardCapacity = (uint64_t)(cardinfo->SD_csd.DeviceSize + 1) * 512 * 1024; cardinfo->CardBlockSize = 512; } /*!< 返回SD响应 */ return status; }

这个函数调用了SD_GetCSDRegister和 SD_GetCIDRegister读取了CSD及CID寄存器的内容, 然后利用CSD寄存器相关的域计算出块大小CardBlockSize和容量CardCapacity。

在计算SD卡块大小和容量的处理中,代码根据SD卡的类型分成了两个分支,其基本计算公式为:SD卡容量 = BLOCKNR * BLOCK_LEN, 其中BLOCKNR为含有的数据块数目,BLOCK_LEN为数据块长度,计算得到的SD卡容量单位为字节。

对于V1.0或SDSC卡的具体计算公式,比较复杂,其说明如下:

BLOCKNR = (C_SIZE+1) * MULT

MULT = 2C_SIZE_MULT+2

BLOCK_LEN = 2READ_BL_LEN

其中:

C_SIZE即代码中的SD_csd.DeviceSize;

C_SIZE_MULT即代码中的SD_csd.DeviceSizeMul;

READ_BL_LEN即代码中的SD_csd.RdBlockLen;

对于SDHC卡则简单得多:

SD卡容量 = (C_SIZE+1) * 512KByte

C_SIZE即代码中的SD_csd.DeviceSize;

36.5.2.6. SD卡数据操作¶

SD卡数据操作一般包括数据读取、数据写入以及存储区擦除,数据读取和写入都可以分为单块操作和多块操作, 由于应用中很少用到擦除操作,本工程仅实现了读写功能。

数据读取操作

读取数据操作与读取CSD寄存器时类似,见 代码清单:SD-17。

代码清单:SD-17 SD_ReadBlock函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54/** * @brief 从SD卡读取一个数据块 * @param pBuffer: 指针,用于存储读取到的数据 * @param ReadAddr: 要读取的SD卡内部地址 * @param BlockSize: 块大小 * @retval SD响应: * - SD_RESPONSE_FAILURE: 失败 * - SD_RESPONSE_NO_ERROR: 成功 */ SD_Error SD_ReadBlock(uint8_t* pBuffer, uint64_t ReadAddr, uint16_t BlockSize) { uint32_t i = 0; SD_Error rvalue = SD_RESPONSE_FAILURE; //SDHC卡块大小固定为512,且读命令中的地址的单位是sector if (SD_Type == SD_TYPE_V2HC) { BlockSize = 512; ReadAddr /= 512; } /*!< 片选CS低电平*/ SD_CS_LOW(); /*!< 发送 CMD17 (SD_CMD_READ_SINGLE_BLOCK) 以读取一个数据块 */ SD_SendCmd(SD_CMD_READ_SINGLE_BLOCK, ReadAddr, 0xFF); /*!< 检查R1响应 */ if (!SD_GetResponse(SD_RESPONSE_NO_ERROR)) { /*!< 检查读取单个数据块的Token */ if (!SD_GetResponse(SD_START_DATA_SINGLE_BLOCK_READ)) { /*!< 读取一个数据块的数据 : NumByteToRead 个数据 */ for (i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3